#include "MacroTests.hpp"
#include "TokenizerTests.hpp"
#include "../Macro.hpp"

namespace CPreProc
{
  class TestMacroProvider : public IMacroProvider
  {
  public:
    virtual Glib::RefPtr<Macro> get_macro(const Glib::ustring &name) const
    {
      std::map<Glib::ustring,Glib::RefPtr<Macro> >::const_iterator it = macros.find(name);
      if(it != macros.end())
        return it->second;
      else
        return Glib::RefPtr<Macro>(NULL);
    }

    std::map<Glib::ustring,Glib::RefPtr<Macro> > macros;
  };

  void MacroTests::test_define_parsing()
  {
    Glib::RefPtr<Macro> macro = Macro::parse("#define b       the replacementlist",false);
    
    assert(macro->get_symbol_name() == "b");
    assert(macro->get_has_param_list() == false);
    
    TokenizerTests::TokenDesc expected_replacement_list[] = 
    {
      {"the",             TokenType_Identifier,     0},
      {" ",               TokenType_WhiteSpace,     0},
      {"replacementlist", TokenType_Identifier,     0},
    };
    TokenizerTests::test_tokens(macro->get_replacement_list(),
      expected_replacement_list,sizeof(expected_replacement_list) / sizeof(expected_replacement_list[0]));
  }

  void MacroTests::test_macro_constructor1()
  {
    Glib::RefPtr<Macro> macro(new Macro("a","foo bar"));
    assert(macro->get_symbol_name() == "a");
    assert(macro->get_has_param_list() == false);

    TokenizerTests::TokenDesc expected_replacement_list[] = 
    {
      {"foo",         TokenType_Identifier,     0},
      {" ",           TokenType_WhiteSpace,     0},
      {"bar",         TokenType_Identifier,     0},
    };
    TokenizerTests::test_tokens(macro->get_replacement_list(),
      expected_replacement_list,sizeof(expected_replacement_list) / sizeof(expected_replacement_list[0]));
  }

  void MacroTests::test_parse_empty_replacement_list()
  {
    Glib::RefPtr<Macro> macro;

    macro = Macro::parse("#define MACRO",false);
    assert(macro->get_symbol_name() == "MACRO");
    assert(macro->get_has_param_list() == false);
    assert(macro->get_replacement_list().size() == 0);

    // if there's whitespace after the symbol name, but no other tokens, 
    // it's still considered empty
    macro = Macro::parse("#define MACRO    ",false);
    assert(macro->get_symbol_name() == "MACRO");
    assert(macro->get_has_param_list() == false);
    assert(macro->get_replacement_list().size() == 0);

    // it should also work for function style macros
    macro = Macro::parse("#define MACRO()    ",false);
    assert(macro->get_symbol_name() == "MACRO");
    assert(macro->get_has_param_list() == true);
    assert(macro->get_num_params() == 0);
    assert(macro->get_replacement_list().size() == 0);
  }

  void MacroTests::test_macro_constructor2()
  {
    std::vector<Glib::ustring> arguments;
    arguments.push_back("foo");
    arguments.push_back("bar");
    Glib::RefPtr<Macro> macro(new Macro("a","foo bar",arguments));
    assert(macro->get_symbol_name() == "a");
    assert(macro->get_has_param_list() == true);
    assert(macro->get_has_var_args() == false);
    assert(macro->get_num_params() == 2);
    assert(macro->get_param_name(0) == "foo");
    assert(macro->get_param_name(1) == "bar");

    TokenizerTests::TokenDesc expected_replacement_list[] = 
    {
      {"foo",         TokenType_Identifier,     0},
      {" ",           TokenType_WhiteSpace,     0},
      {"bar",         TokenType_Identifier,     0},
    };
    TokenizerTests::test_tokens(macro->get_replacement_list(),
      expected_replacement_list,sizeof(expected_replacement_list) / sizeof(expected_replacement_list[0]));
  }

  void MacroTests::test_no_macros()
  {
    TestMacroProvider macro_provider;
    
    Glib::ustring line = "a + b + ( cbcc cbcc )";
    Glib::ustring res = apply_macros(line,macro_provider);
    
    assert(res == line);
  }

  void MacroTests::test_simple_replacement()
  {
    TestMacroProvider macro_provider;
    Glib::RefPtr<Macro> macro(new Macro("b","zzz"));
    macro_provider.macros[macro->get_symbol_name()] = macro;
    
    Glib::ustring line = "a + b+ (cbcc cbcc)";
    Glib::ustring res = apply_macros(line,macro_provider);
    
    assert(res == "a + zzz+ (cbcc cbcc)");
  }

  void MacroTests::test_collapse_whitespace()
  {
    // Whitespace that's a result of a macro replacement must be collapsed into a single space.

    {
      TestMacroProvider macro_provider;
      Glib::RefPtr<Macro> macro(new Macro("b","zzz  \t  aaa   "));
      macro_provider.macros[macro->get_symbol_name()] = macro;

      Glib::ustring line = "a \t+ b+ (cbcc cbcc)";
      Glib::ustring res = apply_macros(line,macro_provider);

      // The whitespace that was part of the macro's replacement list should have been collapsed, 
      // the whitespace that was just part of the normal part of the line should not.
      assert(res == "a \t+ zzz aaa+ (cbcc cbcc)");
    }

    {
      TestMacroProvider macro_provider;
      Glib::RefPtr<Macro> macro = Macro::parse("#define b(a) a",false);
      macro_provider.macros[macro->get_symbol_name()] = macro;

      Glib::ustring line = "a b( c)";
      Glib::ustring res = apply_macros(line,macro_provider);
      assert(res == "a c");

      line = "a b(c     d)";
      res = apply_macros(line,macro_provider);
      assert(res == "a c d");
    }
  }

  void MacroTests::test_vararg_replacement()
  {
    TestMacroProvider macro_provider;
    macro_provider.macros["A"] = Macro::parse("#define A(a,...)        normal arg: (a) var_args: (__VA_ARGS__)",false);

    Glib::ustring line = "A(normal arg,first_v_arg, second v arg   , \t last_v_arg)";
    Glib::ustring res = apply_macros(line,macro_provider);

    assert(res == "normal arg: (normal arg) var_args: (first_v_arg, second v arg , last_v_arg)");
  }

  void MacroTests::test_argument_list()
  {
    std::vector<Glib::ustring> arg_list;
    arg_list.push_back("param1");
    arg_list.push_back("param2");

    TestMacroProvider macro_provider;
    Glib::RefPtr<Macro> macro = Glib::RefPtr<Macro>(new Macro("b","zzz<param1> param2",arg_list));
    macro_provider.macros[macro->get_symbol_name()] = macro;
    
    Glib::ustring line = "a + b(111,(222,333)) + (cbcc cbcc)";
    Glib::ustring res = apply_macros(line,macro_provider);
    
    assert(res == "a + zzz<111> (222,333) + (cbcc cbcc)");
  }

  void MacroTests::test_space_before_arg_list()
  {
    Glib::RefPtr<Macro> macro = Macro::parse("#define A (lala) lala",false);
    assert(macro->get_symbol_name() == "A");
    assert(macro->get_has_param_list() == false);

    TokenizerTests::TokenDesc expected_replacement_list[] = 
    {
      {"(",       TokenType_Punctuator,     Punct_LeftParenthesis},
      {"lala",    TokenType_Identifier,     0},
      {")",       TokenType_Punctuator,     Punct_RightParenthesis},
      {" ",       TokenType_WhiteSpace,     0},
      {"lala",    TokenType_Identifier,     0},
    };
    TokenizerTests::test_tokens(macro->get_replacement_list(),
      expected_replacement_list,sizeof(expected_replacement_list) / sizeof(expected_replacement_list[0]));
  }

  /// funky name for the following test case:
  ///   #define A(a,b)  a + b
  ///   #define B       A ( 100
  ///   #define C       ,200)
  ///   #define D       B C
  /// - evaluate D
  /// where a call to the macro D is composed of the result of 
  /// both B and C.
  void MacroTests::test_recursion_fusion()
  {
    TestMacroProvider macro_provider;
    macro_provider.macros["A"] = Macro::parse("#define A(a,b)  a + b",false);
    macro_provider.macros["B"] = Macro::parse("#define B       A (100",false);
    macro_provider.macros["C"] = Macro::parse("#define C       ,200)",false);
    macro_provider.macros["D"] = Macro::parse("#define D       B C",false);

    Glib::ustring res = apply_macros("D",macro_provider);
    assert(res == "100 + 200");
  }

  void MacroTests::test_sharp_operator()
  {
    TestMacroProvider macro_provider;
    macro_provider.macros["A"] = Macro::parse("#define A(a)      #a",false);

    Glib::ustring res = apply_macros("A(1234)",macro_provider);
    assert(res == "\"1234\"");

    // the quotes around string literals must be escaped,
    // eswell as the \ inside a string literal
    res = apply_macros("A(1234 \"abcd\\ef\")",macro_provider);
    assert(res == "\"1234 \\\"abcd\\\\ef\\\"\"");

    // white space must be collapsed into a single space.
    res = apply_macros("A(1234      +    \t   \"abcd\\ef\")",macro_provider);
    assert(res == "\"1234 + \\\"abcd\\\\ef\\\"\"");

    // tokens that don't need whitespace between them shouldn't have them
    // in the string either.
    res = apply_macros("A(+/)",macro_provider);
    assert(res == "\"+/\"");
  }

  void MacroTests::test_double_sharp_operator()
  {
    {
      TestMacroProvider macro_provider;
      macro_provider.macros["TOKEN_CONCAT"] = Macro::parse("#define TOKEN_CONCAT Name_ ## 1",false);

      Glib::ustring res = apply_macros("TOKEN_CONCAT",macro_provider);
      assert(res == "Name_1");
    }

    {
      TestMacroProvider macro_provider;
      macro_provider.macros["TOKEN_CONCAT"] = Macro::parse("#define TOKEN_CONCAT(a,b) a ## b",false);

      Glib::ustring res = apply_macros("TOKEN_CONCAT(Name_,1)",macro_provider);
      assert(res == "Name_1");
    }

    {
      TestMacroProvider macro_provider;
      macro_provider.macros["TOKEN_CONCAT"] = Macro::parse("#define TOKEN_CONCAT(a,b) a ## b c",false);

      Glib::ustring res = apply_macros("TOKEN_CONCAT(a,)",macro_provider);
      assert(res == "a c");
    }

    {
      TestMacroProvider macro_provider;
      macro_provider.macros["A"] = Macro::parse("#define A   # ## #",false);
      macro_provider.macros["B"] = Macro::parse("#define B   a A b",false);

      Glib::ustring res = apply_macros("B",macro_provider);
      assert(res == "a ## b");
    }
  }

  void MacroTests::test_double_sharp_operator_from_standard()
  {
    TestMacroProvider macro_provider;
    macro_provider.macros["hash_hash"]  = Macro::parse("#define hash_hash # ## #",false);
    macro_provider.macros["mkstr"]      = Macro::parse("#define mkstr(a) # a",false);
    macro_provider.macros["in_between"] = Macro::parse("#define in_between(a) mkstr(a)",false);
    macro_provider.macros["join"]       = Macro::parse("#define join(c, d) in_between(c hash_hash d)",false);

    Glib::ustring res = apply_macros("char p[] = join(x,y)",macro_provider);
    assert(res == "char p[] = \"x ## y\"");
  }

  void MacroTests::test_replacement_list_equality()
  {
    Glib::RefPtr<Macro> a,b;

    a = Macro::parse("#define MACRO   foo + bar",false);
    b = Macro::parse("#define MACRO   foo + bar",false);
    assert(a->replacement_lists_equal(b) == true);

    // any whitespace is considered equal
    a = Macro::parse("#define MACRO   foo + bar",false);
    b = Macro::parse("#define MACRO   foo + \t bar",false);
    assert(a->replacement_lists_equal(b) == true);

    // but whitespace is not optional though
    a = Macro::parse("#define MACRO   foo + bar",false);
    b = Macro::parse("#define MACRO   foo +bar",false);
    assert(a->replacement_lists_equal(b) == false);

    // comparing empty replacement lists
    a = Macro::parse("#define MACRO   ",false);
    b = Macro::parse("#define MACRO ",false);
    assert(a->replacement_lists_equal(b) == true);
    
    // digraphs are NOT the same as their normal forms (wasn't really
    // specifically mentioned in the standard, but this is how
    // it seems to be done in the visual c++ compiler).
    a = Macro::parse("#define MACRO {   }",false);
    b = Macro::parse("#define MACRO <%   %>",false);
    assert(a->replacement_lists_equal(b) == false);
  }
}

